#include "StdAfx.h"
#include "SettingsDef.h"
#include "Settings.h"
#include "Theme.h"
#include "Util.h"
#include "BufReader.h"

#include <atlmisc.h>

#ifdef _DEBUG
#include "crtdbg.h"
#endif

struct SThemeImageMap
{ EThemeImages id; const TCHAR* name; };

#define DECLARE_IMAGE(id) { id, TEXT(#id) }

SThemeImageMap g_imgMap[] = {
    DECLARE_IMAGE(timCheckboxSet),
    DECLARE_IMAGE(timCheckboxUnset),
    DECLARE_IMAGE(timFolderUp),
    DECLARE_IMAGE(timFolderRoot),
    DECLARE_IMAGE(timButtonBg),
    DECLARE_IMAGE(timButtonOk),
    DECLARE_IMAGE(timButtonCancel),
    DECLARE_IMAGE(timButtonDOk),
    DECLARE_IMAGE(timButtonDCancel),
    DECLARE_IMAGE(timButtonTabs),
    DECLARE_IMAGE(timButtonDTabs),
    DECLARE_IMAGE(timArrowLeft),
    DECLARE_IMAGE(timArrowRight),
    DECLARE_IMAGE(timSliderLeft),
    DECLARE_IMAGE(timSliderRight),
    DECLARE_IMAGE(timSliderBg),
    DECLARE_IMAGE(timSliderBar),
    DECLARE_IMAGE(timSliderDBar),
    { timUnknown, NULL }
  };

#define DATAITEM(item, type, def) { TEXT(#item), item, type, def }

// THEME
CIniHandler::SDataItem themeItems[] = {
    DATAITEM(tiWindowBgColor,           ivColor, TEXT("000000")),
    DATAITEM(tiWindowEdgeColor,         ivColor, TEXT("808080")),

    DATAITEM(tiLongTapIndColor,         ivColor, TEXT("808080")),
    DATAITEM(tiLongTapIndFullColor,     ivColor, TEXT("80FF80")),
    DATAITEM(tiLongTapIndWidth,         ivInt,   TEXT("10")),

    DATAITEM(tiListScrollbarWidth,      ivInt,   TEXT("1")),
    DATAITEM(tiListScrollbarBgColor,    ivColor, TEXT("000000")),
    DATAITEM(tiListScrollbarColor,      ivColor, TEXT("808080")),

    DATAITEM(tiListBackground,          ivGrad,  TEXT("404040, 000000")),
    DATAITEM(tiListItemSelBackground,   ivGrad,  TEXT("808080, 404040")),
    DATAITEM(tiListItemSeparatorColor,  ivColor, TEXT("808080")),
    DATAITEM(tiListItemTextColor,       ivColor, TEXT("FFFFFF")),
    DATAITEM(tiListItemSelTextColor,    ivColor, TEXT("FFFFFF")),
    DATAITEM(tiListItemFont,            ivFont,  TEXT("Tahoma, 12, normal, normal")),

    DATAITEM(tiLayoutLblFont,           ivFont,  TEXT("Tahoma, 12, normal, normal")),
    DATAITEM(tiLayoutLblSmallFont,      ivFont,  TEXT("Tahoma, 12, normal, normal")),
    DATAITEM(tiLayoutLblColor,          ivColor, TEXT("808080")),
    DATAITEM(tiLayoutLblSelColor,       ivColor, TEXT("000000")),
    DATAITEM(tiLayoutTextFont,          ivFont,  TEXT("Tahoma, 12, normal, normal")),
    DATAITEM(tiLayoutIntFont,           ivFont,  TEXT("Tahoma, 12, normal, normal")),

    DATAITEM(tiEditBoxColor,            ivColor, TEXT("FFFFFF")),
    DATAITEM(tiEditBoxFont,             ivFont,  TEXT("Tahoma, 12, normal, normal")),
    DATAITEM(tiEditBoxBackground,       ivColor, TEXT("000000")),

    DATAITEM(tiPopupHeaderColor,        ivColor, TEXT("F0F0F0")),
    DATAITEM(tiPopupHeaderFont,         ivFont,  TEXT("Tahoma, 15, bold, normal")),
    DATAITEM(tiPopupHeaderBackground,   ivColor, TEXT("000040")),

    DATAITEM(tiListItemIndent,          ivInt,   TEXT("3")),
    DATAITEM(tiListItemSize,            ivInt,   TEXT("30")),
    DATAITEM(tiSelectorListItemSize,    ivInt,   TEXT("30")),

    { 0 }
  };

CScreenDC::CScreenDC()
{
  HDC sdc = ::GetDC(NULL);
	CreateCompatibleDC(sdc);
	ATLASSERT(m_hDC != NULL);
  m_bmp.CreateCompatibleBitmap(sdc, ::GetSystemMetrics(SM_CXSCREEN), ::GetSystemMetrics(SM_CYSCREEN));
	ATLASSERT(m_bmp.m_hBitmap != NULL);
	m_hBmpOld = SelectBitmap(m_bmp);
  ::ReleaseDC(NULL, sdc);
}

CScreenDC::~CScreenDC()
{
	SelectBitmap(m_hBmpOld);
}

COLORREF CTheme::clrWindowBg;
CBrush   CTheme::brWindowBg;
COLORREF CTheme::clrWindowEdge;

int CTheme::iLongTapIndWidth;
COLORREF CTheme::clrLongTap;
COLORREF CTheme::clrLongTapFull;
CBrush CTheme::brLongTap;
CBrush CTheme::brLongTapFull;

int      CTheme::iListScrollbarWidth;
COLORREF CTheme::clrListScrollbarBg;
COLORREF CTheme::clrListScrollbar;
CBrush   CTheme::brListScrollbarBg;
CBrush   CTheme::brListScrollbar;

CBrush   CTheme::brListBg;
COLORREF CTheme::clrListBg1;
COLORREF CTheme::clrListBg2;
COLORREF CTheme::clrListItemSelBg1;
COLORREF CTheme::clrListItemSelBg2;
COLORREF CTheme::clrListItemSeparator;
CBrush   CTheme::brListItemSeparator;
COLORREF CTheme::clrListItemText;
COLORREF CTheme::clrListItemSelText;
CThemeFont CTheme::fntListItem;

CThemeFont CTheme::fntLayoutLbl;
CThemeFont CTheme::fntLayoutLblSmall;
COLORREF CTheme::clrLayoutLbl;
COLORREF CTheme::clrLayoutLblSel;
CThemeFont CTheme::fntLayoutText;
CThemeFont CTheme::fntLayoutInt;

COLORREF CTheme::clrEditBox;
CThemeFont CTheme::fntEditBox;
COLORREF CTheme::clrEditBoxBg;
CBrush   CTheme::brEditBox;

COLORREF CTheme::clrPopupHeader;
CThemeFont CTheme::fntPopupHeader;
COLORREF CTheme::clrPopupHeaderBg;
CBrush   CTheme::brPopupHeader;

int CTheme::iListItemIndent;
int CTheme::iListItemSize;
int CTheme::iSelectorListItemSize;

CTheme::CImageVector CTheme::images;

bool CTheme::Initialize()
{
  CIniHandler theme;

  CString sFile = CSettings::GetAppFolder();
  sFile += SETTINGS.GetString(sThemePath);
  sFile += TEXT("\\");
  sFile += SETTINGS.GetString(sTheme);
  sFile += THEME_FILE_EXT;

  CZipFile zip;
  if (!zip.Open(sFile))
    return false;

  if (!LoadSettings(zip))
  {
    zip.Close();
    return false;
  }

  if (!LoadImages(zip))
  {
    zip.Close();
    return false;
  }

  zip.Close();
  return true;
}

void CTheme::Terminate()
{
  ReleaseImages();
}

CImage CTheme::GetImage(EThemeImages image)
{
  for (CImageVector::iterator iter = images.begin(); iter != images.end(); iter++)
  {
    SImageData* pData = *iter;
    if (pData && pData->id == image)
    {
      return pData->image;
    }
  }
  return CImage();
}

CImage CTheme::GetImage(const TCHAR* name)
{
  if (!name)
    return CImage();

  for (CImageVector::iterator iter = images.begin(); iter != images.end(); iter++)
  {
    SImageData* pData = *iter;
    if (pData && pData->name.CompareNoCase(name))
    {
      return pData->image;
    }
  }
  return CImage();
}

inline COLOR16 GetGradientPoint(int s, int e, int y, int height)
{
  return (COLOR16)(s + ((e - s) * y) / height);
}

bool CTheme::CreateColor(COLORREF& clr, CIniHandler& ini, EThemeItems id)
{
  CIniColorValue* pVal = static_cast<CIniColorValue*>(ini.GetValue(id));
  if (pVal)
  {
    clr = pVal->m_val;
    return true;
  }
  return false;
}

bool CTheme::CreateGrad(COLORREF& clr1, COLORREF& clr2, CIniHandler& ini, EThemeItems id)
{
  CIniGradValue* pVal = static_cast<CIniGradValue*>(ini.GetValue(id));
  if (pVal)
  {
    clr1 = pVal->m_val1;
    clr2 = pVal->m_val2;
    return true;
  }
  return false;
}

bool CTheme::CreateFont(CThemeFont& font, CIniHandler& ini, EThemeItems id)
{
  CIniFontValue* pVal = static_cast<CIniFontValue*>(ini.GetValue(id));
  if (pVal)
  {
    font.size = pVal->m_size;
    font.bold = pVal->m_bold;
    font.italic = pVal->m_italic;
    font.font.CreateFont(pVal->m_size, 0, 0, 0, pVal->m_bold ? FW_BOLD : FW_NORMAL, pVal->m_italic, FALSE, FALSE, 
      DEFAULT_CHARSET, OUT_RASTER_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
      pVal->m_name);
    return font.font.m_hFont != NULL;
  }
  return false;
}

bool CTheme::CreatePen(CPen& pen, CIniHandler& ini, EThemeItems id)
{
  CIniPenValue* pVal = static_cast<CIniPenValue*>(ini.GetValue(id));
  if (pVal)
  {
    pen.CreatePen(pVal->m_style, pVal->m_width, pVal->m_color);
    return pen.m_hPen != NULL;
  }
  return false;

}

void CTheme::DrawGradientGDI(HDC tdc, const RECT& iRect, COLORREF StartRGB, COLORREF EndRGB)
{
          
  unsigned int Shift = 8;
  TRIVERTEX        vert[2] ;
  GRADIENT_RECT    gRect;
  vert [0] .x      = iRect.left;
  vert [0] .y      = iRect.top;
  vert [0] .Red    = GetRValue(StartRGB) << Shift;
  vert [0] .Green  = GetGValue(StartRGB) << Shift;
  vert [0] .Blue   = GetBValue(StartRGB) << Shift;
  vert [0] .Alpha  = 0x0000;
  vert [1] .x      = iRect.right;
  vert [1] .y      = iRect.bottom; 
  vert [1] .Red    = GetRValue(EndRGB) << Shift;
  vert [1] .Green  = GetGValue(EndRGB) << Shift;
  vert [1] .Blue   = GetBValue(EndRGB) << Shift;
  vert [1] .Alpha  = 0x0000;
  gRect.UpperLeft  = 0;
  gRect.LowerRight = 1;
  GradientFill(tdc, vert, 2, &gRect, 1, GRADIENT_FILL_RECT_V);
}

void CTheme::DrawGradientGDI(HDC tdc, const RECT& iClip, const RECT& iRect, COLORREF StartRGB, COLORREF EndRGB)
{
  if (iRect.bottom == iRect.top)
    return;

  unsigned int Shift = 8;
  TRIVERTEX        vert[2] ;
  GRADIENT_RECT    gRect;
  vert [0] .x      = max(iClip.left, iRect.left);
  vert [0] .y      = max(iClip.top, iRect.top);
  if (vert [0] .y == iRect.top)
  {
    vert [0] .Red    = GetRValue(StartRGB) << Shift;
    vert [0] .Green  = GetGValue(StartRGB) << Shift;
    vert [0] .Blue   = GetBValue(StartRGB) << Shift;
  }
  else
  {
    int height = iRect.bottom - iRect.top;
    vert [0] .Red    = GetGradientPoint(GetRValue(StartRGB), GetRValue(EndRGB), iClip.top - iRect.top, height) << Shift;
    vert [0] .Green  = GetGradientPoint(GetGValue(StartRGB), GetGValue(EndRGB), iClip.top - iRect.top, height) << Shift;
    vert [0] .Blue   = GetGradientPoint(GetBValue(StartRGB), GetBValue(EndRGB), iClip.top - iRect.top, height) << Shift;
  }
  vert [0] .Alpha  = 0x0000;
  vert [1] .x      = min(iClip.right, iRect.right);
  vert [1] .y      = min(iClip.bottom, iRect.bottom); 
  if (vert [1] .y == iRect.bottom)
  {
    vert [1] .Red    = GetRValue(EndRGB) << Shift;
    vert [1] .Green  = GetGValue(EndRGB) << Shift;
    vert [1] .Blue   = GetBValue(EndRGB) << Shift;
  }
  else
  {
    int height = iRect.bottom - iRect.top;
    vert [1] .Red    = GetGradientPoint(GetRValue(StartRGB), GetRValue(EndRGB), iClip.bottom - iRect.top, height) << Shift;
    vert [1] .Green  = GetGradientPoint(GetGValue(StartRGB), GetGValue(EndRGB), iClip.bottom - iRect.top, height) << Shift;
    vert [1] .Blue   = GetGradientPoint(GetBValue(StartRGB), GetBValue(EndRGB), iClip.bottom - iRect.top, height) << Shift;
  }
  vert [1] .Alpha  = 0x0000;
  gRect.UpperLeft  = 0;
  gRect.LowerRight = 1;
  GradientFill(tdc, vert, 2, &gRect, 1, GRADIENT_FILL_RECT_V);
}

void CTheme::BltAlpha(HDC hdcDest, int nXOriginDest, int nYOriginDest,
              int nWidthDest, int nHeightDest,
              HDC hdcSrc, int nXOriginSrc, int nYoriginSrc,
              int nWidthSrc, int nHeightSrc,
              BYTE alpha)
{
  BLENDFUNCTION bf;
  bf.BlendOp = AC_SRC_OVER;
  bf.BlendFlags = 0;
  bf.SourceConstantAlpha = alpha;
  bf.AlphaFormat = 0;
  AlphaBlend(hdcDest, nXOriginDest, nYOriginDest, nWidthDest, nHeightDest, 
    hdcSrc, nXOriginSrc, nYoriginSrc, nWidthSrc, nHeightSrc, bf);
}

COLORREF CTheme::GetMedianColor(COLORREF clr1, COLORREF clr2)
{
  return RGB(GetGradientPoint(GetRValue(clr1), GetRValue(clr2), 1, 2),
             GetGradientPoint(GetGValue(clr1), GetGValue(clr2), 1, 2),
             GetGradientPoint(GetBValue(clr1), GetBValue(clr2), 1, 2));
}

bool CTheme::LoadSettings(CZipFile& zip)
{
  if (!zip.GoToFile(THEME_INI_FILE))
    return false;

  // get file data
  DWORD dwSize = 0;
  if (!zip.GetFileInfo(dwSize) || dwSize == 0)
    return false;

  HLOCAL pBuffer = ::LocalAlloc(LMEM_FIXED, dwSize);
  if (!pBuffer)
    return false;

  CIniHandler theme;
  bool loaded = false;

  if (zip.GetFileData(static_cast<BYTE*>(pBuffer), dwSize))
  {
    CBufReader br;
    br.Open(static_cast<BYTE*>(pBuffer), dwSize);
    loaded = theme.Create(br, themeItems);
  }

  ::LocalFree(pBuffer);

  if (!loaded)
    return false;

  CreateColor(clrWindowBg, theme, tiWindowBgColor);
  brWindowBg.CreateSolidBrush(clrWindowBg);
  CreateColor(clrWindowEdge, theme, tiWindowEdgeColor);

  iLongTapIndWidth = theme.GetInt(tiLongTapIndWidth);
  CreateColor(clrLongTap, theme, tiLongTapIndColor);
  CreateColor(clrLongTapFull, theme, tiLongTapIndFullColor);
  brLongTap.CreateSolidBrush(clrLongTap);
  brLongTapFull.CreateSolidBrush(clrLongTapFull);

  iListScrollbarWidth = theme.GetInt(tiListScrollbarWidth);
  CreateColor(clrListScrollbarBg, theme, tiListScrollbarBgColor);
  CreateColor(clrListScrollbar, theme, tiListScrollbarColor);
  brListScrollbarBg.CreateSolidBrush(clrListScrollbarBg);
  brListScrollbar.CreateSolidBrush(clrListScrollbar);

  CreateGrad(clrListBg1, clrListBg2, theme, tiListBackground);
  brListBg.CreateSolidBrush(CTheme::GetMedianColor(clrListBg1, clrListBg2));
  CreateGrad(clrListItemSelBg1, clrListItemSelBg2, theme, tiListItemSelBackground);
  CreateColor(clrListItemSeparator, theme, tiListItemSeparatorColor);
  brListItemSeparator.CreateSolidBrush(clrListItemSeparator);
  CreateColor(clrListItemText, theme, tiListItemTextColor);
  CreateColor(clrListItemSelText, theme, tiListItemSelTextColor);
  CreateFont(fntListItem, theme, tiListItemFont);

  CreateFont(fntLayoutLbl, theme, tiLayoutLblFont);
  CreateFont(fntLayoutLblSmall, theme, tiLayoutLblSmallFont);
  CreateColor(clrLayoutLbl, theme, tiLayoutLblColor);
  CreateColor(clrLayoutLblSel, theme, tiLayoutLblSelColor);
  CreateFont(fntLayoutText, theme, tiLayoutTextFont);
  CreateFont(fntLayoutInt, theme, tiLayoutIntFont);

  CreateColor(clrEditBox, theme, tiEditBoxColor);
  CreateFont(fntEditBox, theme, tiEditBoxFont);
  CreateColor(clrEditBoxBg, theme, tiEditBoxBackground);
  brEditBox.CreateSolidBrush(clrEditBoxBg);

  CreateColor(clrPopupHeader, theme, tiPopupHeaderColor);
  CreateFont(fntPopupHeader, theme, tiPopupHeaderFont);
  CreateColor(clrPopupHeaderBg, theme, tiPopupHeaderBackground);
  brPopupHeader.CreateSolidBrush(clrPopupHeaderBg);

  iListItemIndent = theme.GetInt(tiListItemIndent);
  iListItemSize = theme.GetInt(tiListItemSize);
  iSelectorListItemSize = theme.GetInt(tiSelectorListItemSize);

  return true;
}

bool CTheme::LoadImages(CZipFile& zip)
{
  ReleaseImages();

  IImagingFactory *pImgFactory = NULL;

  zip.GoToFirstFile();

  // Create the imaging factory.
  bool result = false;
  HRESULT hr = ::CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void **)&pImgFactory);
  if (SUCCEEDED(hr))
  {
    DWORD dwSize = 0;
    TCHAR szName[512];
    result = true;

    try
    {
      do
      {
        if (zip.GetFileInfo(dwSize, szName, 512) && dwSize > 0 && CUtil::IsImageFile(szName))
        {
          result &= ReadImage(zip, dwSize, szName, pImgFactory);
        }
      }
      while(zip.GoToNextFile());

      pImgFactory->Release();
    }
    catch(...)
    {
      pImgFactory->Release();
      throw;
    }
  }

  return result;
}

bool CTheme::ReadImage(CZipFile& zip, DWORD dwSize, const TCHAR* szName, IImagingFactory* pFactory)
{
  bool res = false;

  HLOCAL pBuffer = ::LocalAlloc(LMEM_FIXED, dwSize);
  if (pBuffer)
  {
    IImage* pImage = NULL;
    if (zip.GetFileData(static_cast<BYTE*>(pBuffer), dwSize))
    {
      if (ReadImageFromBuf(static_cast<BYTE*>(pBuffer), dwSize, &pImage, pFactory))
      {
        SImageData* pImageData = new SImageData();
        if (pImageData)
        {
          pImageData->name = szName;
          int ext = pImageData->name.ReverseFind(TEXT('.'));
          if (ext != -1)
            pImageData->name.Truncate(ext);

          for (int i = 0; g_imgMap[i].id != 0; i++)
          {
            if (g_imgMap[i].name && pImageData->name.CompareNoCase(g_imgMap[i].name) == 0)
            {
              pImageData->id = g_imgMap[i].id;
              break;
            }
          }

          ImageInfo ii;
          if (SUCCEEDED(pImage->GetImageInfo(&ii)))
          {
            pImageData->image.width = ii.Width;
            pImageData->image.height = ii.Height;
          }

          pImageData->image.image = pImage;
          images.push_back(pImageData);
          res = true;
        }
        else
          pImage->Release();
      }
    }
    ::LocalFree(pBuffer);
  }

  return res;
}

bool CTheme::ReadImageFromBuf(const void* pBuf, DWORD nSize, IImage** pImage, IImagingFactory* pFactory)
{
  if (!pBuf || !nSize)
    return false;

  HRESULT hr = pFactory->CreateImageFromBuffer(pBuf, nSize, BufferDisposalFlagNone, pImage);

  return SUCCEEDED(hr);
}

bool CTheme::CreateBitmap(IImage* pImage, CBitmap& bitmap, int& width, int& height, COLORREF bgColor)
{
  if (!pImage)
    return false;

  HRESULT hr;

  ImageInfo ii;
  hr = pImage->GetImageInfo(&ii);
  if (FAILED(hr) || (ii.Width == 0 && ii.Height == 0))
    return false;

  CWindowDC dc(::GetDesktopWindow());

  CDC memDC;
  if (memDC.CreateCompatibleDC(dc))
  {
    if (bitmap.CreateCompatibleBitmap(dc, ii.Width, ii.Height))
    {
      HBITMAP hbmOld = memDC.SelectBitmap(bitmap);

      memDC.FillSolidRect(0, 0, ii.Width, ii.Height, bgColor);

      CRect rc(0, 0, ii.Width, ii.Height);
      hr = pImage->Draw(memDC, &rc, NULL);

      memDC.SelectBitmap(hbmOld);

      if (SUCCEEDED(hr))
      {
        width = ii.Width;
        height = ii.Height;
        return true;
      }
      else
      {
        bitmap.DeleteObject();
      }
    }
  }
  return false;
}

void CTheme::ReleaseImages()
{
  for (CImageVector::iterator iter = images.begin(); iter != images.end(); iter++)
  {
    if (*iter)
      delete *iter;
  }
  images.clear();
}